﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Reflection.Emit;

namespace Axial
{
	public class ContinueConditionStatement : ConditionStatement
	{
		public ContinueConditionStatement(CodeStatement oldCondition)
		{
			Instructions = oldCondition.Instructions;
			Parameters = oldCondition.Parameters;
			Code = oldCondition.Code;
			StackUsage = oldCondition.StackUsage;
			LeftoverStack = oldCondition.LeftoverStack;
		}
	}

	public class BreakConditionStatement : ConditionStatement
	{
		public BreakConditionStatement(CodeStatement oldCondition)
		{
			Instructions = oldCondition.Instructions;
			Parameters = oldCondition.Parameters;
			Code = oldCondition.Code;
			StackUsage = oldCondition.StackUsage;
			LeftoverStack = oldCondition.LeftoverStack;
		}
	}

	public class ReturnStatement : CodeStatement
	{
		public override string Code
		{
			get { return "return"; }
		}

		public ReturnStatement(int startOffset)
		{
			Instructions = new ILInstruction[]
			{ 
				new ILInstruction 
				{
					Code = OpCodes.Nop,
					Offset = startOffset
				}
			};
		}
	}

	public class BreakStatement : CodeStatement
	{
		public override string Code
		{
			get { return "break"; }
		}

		public BreakStatement(int startOffset)
		{
			Instructions = new ILInstruction[]
			{ 
				new ILInstruction 
				{
					Code = OpCodes.Nop,
					Offset = startOffset
				}
			};
		}
	}

	public class ContinueStatement : CodeStatement
	{
		public override string Code
		{
			get { return "continue"; }
		}

		public ContinueStatement(int startOffset)
		{
			Instructions = new ILInstruction[]
			{ 
				new ILInstruction 
				{
					Code = OpCodes.Nop,
					Offset = startOffset
				}
			};
		}
	}

	public class JumpStatement : CodeStatement
	{
		public JumpStatement(IEnumerable<ILInstruction> instructions, object[] parameters)
			: base(instructions, parameters)
		{
		}

		public JumpStatement()
		{
		}

		public virtual int FlowTarget
		{
			get { return (int)LastInstruction.Operand; }
		}

		public override string ToString()
		{
			return string.Format("{0}: {1} (jump to {2})", StartOffset, Code, FlowTarget);
		}
	}

	public class ConditionStatement : JumpStatement
	{
		// HACK: so tertiary statements can do opposite conditions
		public string OppositeConditionCache { get; set; }

		public ConditionStatement(IEnumerable<ILInstruction> instructions, object[] parameters)
			: base(instructions, parameters)
		{
		}

		public ConditionStatement()
		{
		}

		public virtual string Condition
		{
			get { return Code.Substring(4, Code.Length - 5); }
		}
		public virtual string OppositeCondition
		{
			get
			{
				if (OppositeConditionCache != null)
					return OppositeConditionCache;

				return ConditionHelper.InvertCondition(Condition);
			}
		}
	}

	public class CodeStatement : IJSWriterable
	{
		public ILInstruction[] Instructions { get; protected set; }
		public object[] Parameters { get; protected set; }
		public virtual string Code { get; protected set; }
		public int StackUsage { get; protected set; }
		public Stack LeftoverStack { get; protected set; }

		public int StartOffset
		{
			get { return Instructions[0].Offset; }
		}
		public ILInstruction LastInstruction
		{
			get { return Instructions.Last(); }
		}
		public bool IsReturn
		{
			get { return LastInstruction.Code == OpCodes.Ret; }
		}

		protected CodeStatement()
		{
			Instructions = new ILInstruction[1] { new ILInstruction { Code = OpCodes.Nop } };
		}

		protected CodeStatement(IEnumerable<ILInstruction> instructions, object[] parameters)
		{
			Instructions = instructions.ToArray();
			Parameters = parameters;

			CalculateStackUsage();

			// determine code
			Stack stack = new Stack();
			foreach (ILInstruction inst in Instructions)
			{
				string statement = JSWebTranslator.HandleInstruction(inst, Parameters, stack);
				if (statement != null)
					Code = statement;
			}
			LeftoverStack = new Stack(stack);
		}

		public static CodeStatement Create(IEnumerable<ILInstruction> instructions, object[] parameters)
		{
			var lastInstruction = instructions.Last();
			if (lastInstruction.IsConditionalBranch())
				return new ConditionStatement(instructions, parameters);
			if (lastInstruction.IsUnconditionalBranch())
				return new JumpStatement(instructions, parameters);
			return new CodeStatement(instructions, parameters);
		}

		public static CodeStatement CreateTertiary(IEnumerable<CodeStatement> toCombine)
		{
			CodeStatement statement;
			var lastInstruction = toCombine.Last().LastInstruction;
			if (lastInstruction.IsConditionalBranch())
				statement = new ConditionStatement();
			else if (lastInstruction.IsUnconditionalBranch())
				statement =new JumpStatement();
			else statement = new CodeStatement();

			statement.Instructions = toCombine.SelectMany(s => s.Instructions).ToArray();
			statement.Parameters = toCombine.First().Parameters;

			var statements = toCombine.ToList();
			var twoLoad = CodeStatement.Create(new ILInstruction[] { statements[2].Instructions[0] }, statements[2].Parameters);

			ConditionStatement conditionStatement = statements[0] as ConditionStatement;
			string condition = conditionStatement.Condition;
			object ifFalse = statements[1].LeftoverStack.Peek();
			object ifTrue = twoLoad.LeftoverStack.Peek();

			string value = string.Format("{0} ? {1} : {2}", condition, ifTrue, ifFalse);
			if (statements.Last().GetType() != typeof(CodeStatement))
				value = string.Format("({0})", value);
			Stack stack = new Stack();
			stack.Push(value);

			if (statement.GetType() != typeof(ConditionStatement))
			{
				var popInstructions = statements[2].Instructions.Skip(1);
				foreach (var inst in popInstructions)
					statement.Code = JSWebTranslator.HandleInstruction(inst, statement.Parameters, stack);
			}
			else
			{
				var cs = statement as ConditionStatement;

				var popInstructions = statements[2].Instructions.Skip(1).ToArray();
				popInstructions = popInstructions.TakeWhile(il => il != popInstructions.Last()).ToArray();
				foreach (var inst in popInstructions)
					JSWebTranslator.HandleInstruction(inst, statement.Parameters, stack);
				
				Stack stackCopy = new Stack(stack);
				statement.Code = JSWebTranslator.HandleInstruction(statement.Instructions.Last(), statement.Parameters, stack);
				
				Stack fakeStack = new Stack();
				fakeStack.Push(1);
				fakeStack.Push(2);
				string oppositeCode = JSWebTranslator.HandleInstruction(statement.Instructions.Last(), statement.Parameters, fakeStack);
				// same code as ConditionStatement.Code
				oppositeCode = ConditionHelper.InvertCondition(oppositeCode.Substring(4, oppositeCode.Length - 5));
				oppositeCode = oppositeCode.Substring(1, oppositeCode.Length - 2);
				oppositeCode = string.Format("{0}{1}{2}", stackCopy.Pop(), oppositeCode, stackCopy.Pop());
				cs.OppositeConditionCache = oppositeCode;
			}

			return statement;
		}

		private void CalculateStackUsage()
		{
			foreach (ILInstruction inst in Instructions)
			{
				StackUsage += inst.Code.StackBehaviourPush.GetStackChangeCount();
				StackUsage -= inst.Code.StackBehaviourPop.GetStackChangeCount();
				// OpCodes.Return is Varpop and only pops if one's there
				if (inst.Code.StackBehaviourPush == StackBehaviour.Varpop && StackUsage == -1)
					StackUsage = 0;
			}
		}

		public override string ToString()
		{
			return StartOffset + ": " + Code;
		}

		public virtual JSWriter ToJSWriter()
		{
			JSWriter writer = new JSWriter();
			writer.AddStatement(Code);
			return writer;
		}
	}
}
